<?php
namespace Cola;
/**
 * 错误和异常处理类
 */
class Exception extends \ErrorException
{
	public static $data = [];

    public function __construct($e, $data = [])
	{
		if ( ! $e instanceof Exception)
		{
			self::$data = $data;
			parent::__construct($e);
		}
		else
		{
			self::report($e, $data);
		}
	}

	/**
	 * 注册异常或错误处理
	 * @return void
	 */
	public static function register()
	{
		set_error_handler([__CLASS__, 'appError']);
		set_exception_handler([__CLASS__, 'appException']);
		register_shutdown_function([__CLASS__, 'appShutdown']);
	}

	/**
	 * 处理异常（如语法解析错误、自定义抛出异常）
	 * @param Exception|\ParseError $e
	 */
	public static function appException($e)
	{
		self::report($e);
	}

	/**
	 * 处理错误（如行时的错误、警告、通知）
	 * @param integer $errno   错误编号
	 * @param integer $errstr  详细错误信息
	 * @param string  $errfile 出错的文件
	 * @param integer $errline 出错行号
	 * @param array $errcontext
     */
	public static function appError($errno, $errstr, $errfile = '', $errline = 0, $errcontext = [])
	{
		$e = new \ErrorException($errstr, $errno, 1, $errfile, $errline);
		self::report($e);
		//exit();
	}

	/**
	 * 脚本执行结束时（不管是正常结束，运行超时，异常或出错而中断）会自动调用该方法
	 */
	public static function appShutdown()
	{

	}

	/**
	 * Report or log an exception.
	 *
	 * @param \Exception $e
	 * @param array $data
	 * @return void
	 */
	public static function report($e, $data = [])
	{
		if ( ! $e instanceof Exception)
		{
			$e = new Exception($e);
		}

		$log = [];

		$log['file'] = $e->getFile();
		$log['line'] = $e->getLine();

		// 错误消息
		$log['msg'] = $e->getMessage();

		if(stripos($log['msg'], 'Stack trace') !== false)
		{
			$log['msg'] = explode('Stack trace', $log['msg'])[0];
		}

		if(stripos($log['msg'], ' in ') !== false)
		{
			$msg = explode(' in ', $log['msg']);

			if( ! empty($msg[1]))
			{
				$file_and_line = preg_split('/:| on line /', $msg[1]);
				if( ! empty($file_and_line[1]))
				{
					list($log['file'], $log['line']) = $file_and_line;
					$log['msg'] = $msg[0];
				}
			}
		}

		$log['msg'] = str_replace(['Error:', 'Parse error:'], '', $log['msg']); // 去掉一些多余的字符

		// 文件名
		$log['basename'] = pathinfo($log['file'], PATHINFO_BASENAME);

		// 扩展信息
		$log['extend'] = $data ? : self::$data;
		empty($log['extend']['err_info']) or $log['msg'] .= $log['extend']['err_info'];

		// 代码追踪信息
		$traces = $e->getTrace(); // 数组格式
		$trace = $e->getTraceAsString(); // 字符串格式

		if(APP_DEV)
		{
			// 定位上下各9行的代码
			$source = self::getSourceCode($log['file'], (int)$log['line']);
		}
		// 写入日志
//		logfile('error', $log);
		self::$data = [];

		ob_clean();

        // 忽略调用已弃用方法的错误
        if(self::isDeprecated($log['msg'])) {
            return;
        }
        // 线上正式服一般不报notice错误
        if( APP_DEV || ! self::isNotice($log['msg']))
        {
            if(Request::isAjax())
            {
                $message = APP_DEBUG ? $log['msg'] : 'system busy';

                echo json_encode([
                    'message' => $message
                ]);
            }
            else
            {
                require __DIR__ . '/../Application/View/exception.php';
            }
            exit;
        }
	}

    /**
     * 检查信息是否是notice
     *
     * @param $msg
     * @return bool
     */
	protected static function isNotice($msg)
    {
        return strpos($msg, 'Undefined variable') !== false || strpos($msg, 'Undefined index') !== false || strpos($msg, 'Undefined offset') !== false;
    }

    protected static function isDeprecated($msg)
    {
        return strpos($msg, 'is deprecated') !== false;
    }

    /**
     * 确定错误类型是否致命
     *
     * @param  int $type
     * @return bool
     */
    protected static function isFatal($type)
    {
        return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]);
    }

    /**
     * 获取出错文件内容
     * 获取错误的前9行和后9行
     * @param string $file 绝对路径及文件
     * @param int $line 发生错误的行数
     * @return array 错误文件内容
     */
    protected static function getSourceCode($file, $line)
    {
        $source = [];
        if(is_file($file))
        {
            // 读取前9行和后9行
            $first = ($line - 9 > 0) ? $line - 9 : 1;

            try {
                $contents = file($file);
                $source   = [
                    'first'  => $first,
                    'source' => array_slice($contents, $first - 1, 19),
                ];
            } catch (\Exception $e) {
            }
        }
        return $source;
    }
}